using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Xml;

namespace Woz
{
    /// <summary>
    /// image class used by image tool
    /// </summary>
    internal class Image : Woz.Entity
    {
        static Image()
        {
            // a sixty percent semitransparent color matrix
            ColorMatrix colorMatrix = new ColorMatrix(new float[5][] {
                new float[5]{1.0f, 0.0f, 0.0f, 0.0f, 0.0f},
                new float[5]{0.0f, 1.0f, 0.0f, 0.0f, 0.0f},
                new float[5]{0.0f, 0.0f, 1.0f, 0.0f, 0.0f},
                new float[5]{0.0f, 0.0f, 0.0f, 0.6f, 0.0f},
                new float[5]{0.0f, 0.0f, 0.0f, 0.0f, 1.0f} });

            // create the semi transparent image attributes
            sSemiTransparentAttributes = new ImageAttributes();

            // set its color matrix to the sixty percent semitransparent color matrix
            sSemiTransparentAttributes.SetColorMatrix(colorMatrix);

            // create the cached images
            sCachedImages = new List<CachedImage>();
        }

        /// <summary>
        /// creates a new image using the xml stream and path
        /// </summary>
        /// <param name="pStream">the xml stream to load the image data from</param>
        /// <param name="pBoundingBox">the boundin box of the entity</param>
        /// <param name="pParams">the path of the xml stream</param>
        public Image(XmlReader pStream, Rectangle pBoundingBox, string pPath)
            : base(pBoundingBox.Location, pBoundingBox.Size)
        {
            pStream.ReadStartElement("Image", "urn:Woz");
            this.mImage = LoadImage(Path.Combine(pPath, pStream.ReadElementString("File")));
            pStream.ReadEndElement();
        }

        /// <summary>
        /// default constructor
        /// </summary>
        /// <param name="pFilename">the path to the image</param>
        /// <param name="pPosition">the position of the image</param>
        public Image(string pFilename, Point pPosition) : base(pPosition, Size.Empty)
        {
            // create the image
            this.mImage = LoadImage(pFilename);

            // the inkspace point
            Point inkSpace = new Point(this.mImage.Image.Width, this.mImage.Image.Height);
            inkSpace = Woz.InkSpaceConverter.Instance.PixelToInkSpace(inkSpace);

            // set the bounding box
            this.mBoundingBox.Width = inkSpace.X;
            this.mBoundingBox.Height = inkSpace.Y;
        }

        /// <summary>
        /// clone constructor
        /// </summary>
        /// <param name="pImage">image to clone</param>
        private Image(Woz.Image pImage)
            : this(pImage.mImage.Filename, pImage.mBoundingBox.Location)
        {
            this.Locked = pImage.Locked;
        }

        /// <summary>
        /// gets whether the image is semitransparent or not
        /// </summary>
        internal bool Semitransparent
        {
            set
            {
                this.mSemiTransparent = value;
            }
        }

        /// <summary>
        /// figure out whether or not entity lies within
        /// given pair of points 
        /// </summary>
        /// <param name="pX">x position to check</param>
        /// <param name="pY">y position to check</param>
        /// <returns>true if entity lies within pX, pY</returns>
        public override bool HitTest(int pX, int pY)
        {
            return this.mBoundingBox.Contains(pX, pY);
        }

        /// <summary>
        /// tells entity to draw itself using the graphics object
        /// </summary>
        /// <param name="pGraphics">the graphics object to use</param>
        public override void Draw(Graphics pGraphics)
        {
            System.Drawing.Drawing2D.SmoothingMode smoothingMode = pGraphics.SmoothingMode;
            pGraphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.Default;
            //pGraphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;

            if (this.mSemiTransparent)
            {
                pGraphics.DrawImage(this.mImage.Image, this.mBoundingBox,
                    0, 0, this.mImage.Image.Width, this.mImage.Image.Height,
                    GraphicsUnit.Pixel,
                    sSemiTransparentAttributes);
                
                //pGraphics.DrawRectangle(Pens.Black, this.mBoundingBox.X+1, this.mBoundingBox.Y+1, this.mBoundingBox.Width - 2, this.mBoundingBox.Height - 2);
            }
            else
            {
                pGraphics.DrawImage(this.mImage.Image, this.mBoundingBox);
            }

            pGraphics.SmoothingMode = smoothingMode;
        }

        /// <summary>
        /// release all memory associated with this entity
        /// </summary>
        public override void Dispose()
        {
        }

        /// <summary>
        /// create an exact copy of this entity and return it
        /// </summary>
        /// <returns>an exact duplicate of this entity</returns>
        public override object Clone()
        {
            return new Woz.Image(this);
        }

        /// <summary>
        /// checks whether or not this entity supports an attribute
        /// </summary>
        /// <param name="pAttribute">the name of the attribute</param>
        /// <returns>true if attribute is supported</returns>
        public override bool SupportsAttribute(string pAttribute)
        {
            // if all else fails just return false
            return false;
        }

        /// <summary>
        /// gets a reference to the attribute with the given attribute name
        /// </summary>
        /// <param name="pAttribute">the name of the attribute</param>
        /// <returns>a reference to the attribute</returns>
        public override object GetAttribute(string pAttribute)
        {
            return null;
        }

        /// <summary>
        /// saves a reference to the attribute with the given attribute name
        /// </summary>
        /// <param name="pAttribute">the name of the attribute</param>
        /// <param name="pObject">a reference to the attribute</param>
        public override void SetAttribute(string pAttribute, object pObject)
        {
        }

        /// <summary>
        /// reset the cached images
        /// </summary>
        public static void Reset()
        {
            sCachedImages.Clear();
        }

        /// <summary>
        /// returns a string representing the image
        /// </summary>
        /// <returns>a string representing the image</returns>
        public override string ToString()
        {
            return String.Format("Image {0}", Path.GetFileName(this.mImage.Filename));
        }

        /// <summary>
        /// the actual moving of the entity happens here
        /// </summary>
        /// <param name="pDeltaX">the relative x amount to move entity by</param>
        /// <param name="pDeltaY">the relative y amount to move entity by</param>
        protected override void OnMove(int pDeltaX, int pDeltaY)
        {
            this.mBoundingBox.X += pDeltaX;
            this.mBoundingBox.Y += pDeltaY;
        }

        /// <summary>
        /// the actual scaling of the entity happens here
        /// </summary>
        /// <param name="pDeltaWidth">the relative amount of width</param>
        /// <param name="pDeltaHeight">the relative amount of height</param>
        protected override void OnScale(int pDeltaWidth, int pDeltaHeight)
        {
            this.mBoundingBox.Width += pDeltaWidth;
            this.mBoundingBox.Height += pDeltaHeight;
        }

        /// <summary>
        /// the actual saving of the entity happens here
        /// </summary>
        /// <param name="pStream">the stream to save to</param>
        /// <param name="pPath">the path of the temporary files</param>
        protected override void OnSave(XmlWriter pStream, string pPath)
        {
            // save the cached images
            SaveCachedImages(pPath);

            // write the file name
            pStream.WriteStartElement("Image", "urn:Woz");
            pStream.WriteElementString("File", Path.GetFileName(this.mImage.Filename));
            pStream.WriteEndElement();
        }

        /// <summary>
        /// the bitmap image
        /// </summary>
        private CachedImage mImage;

        /// <summary>
        /// whether or not the image is semitransparent
        /// </summary>
        private bool mSemiTransparent;

        /// <summary>
        /// semi transparent image attributes applied to an image to make it semitransparent
        /// </summary>
        static private readonly ImageAttributes sSemiTransparentAttributes;

        #region Cached Images

        /// <summary>
        /// cached image structure
        /// </summary>
        private class CachedImage
        {
            /// <summary>
            /// cached image structure
            /// </summary>
            /// <param name="pFilename">the full path of the file</param>
            public CachedImage(string pFilename)
            {
                // store the filename
                this.Filename = pFilename;

                // store the image
                this.Image = System.Drawing.Image.FromFile(pFilename);

                // store the reference count
                this.RefCount = 1;
            }

            /// <summary>
            /// the full path to the image
            /// </summary>
            public string Filename;

            /// <summary>
            /// the System.Drawing.Image instance
            /// </summary>
            public System.Drawing.Image Image;

            /// <summary>
            /// the reference count of this image
            /// </summary>
            public int RefCount;
        }

        /// <summary>
        /// a list of cached images
        /// </summary>
        private static List<CachedImage> sCachedImages;

        /// <summary>
        /// gets an image using a given path
        /// </summary>
        /// <param name="pFilename">the path of the image</param>
        /// <returns>an instance of the cached image</returns>
        private static CachedImage LoadImage(string pFilename)
        {
            // first get the absolute full path to the filename
            pFilename = Path.GetFullPath(pFilename);

            // now search for this and load it up
            foreach (CachedImage ci in sCachedImages)
            {
                // check to see if the image already exists
                if (ci.Filename.Equals(pFilename, StringComparison.CurrentCultureIgnoreCase))
                {
                    // increment the reference count
                    ci.RefCount++;

                    // return the image
                    return ci;
                }
            }

            // create a new one
            CachedImage cache = new CachedImage(pFilename);
            
            // add it to cached images
            sCachedImages.Add(cache);

            // return the image
            return cache;
        }

        /// <summary>
        /// find the cached image associated with an actual image
        /// </summary>
        /// <param name="pImage">the image to search for</param>
        /// <returns>an instance of cached image</returns>
        private static CachedImage FindCachedImage(System.Drawing.Image pImage)
        {
            // search for this and get the filename
            foreach (CachedImage ci in sCachedImages)
            {
                // check to see if the image already exists
                if (ci.Image == pImage)
                {
                    // return it
                    return ci;
                }
            }

            // you should never get here
            return null;
        }

        /// <summary>
        /// saves the cached images as jpegs to the given path
        /// </summary>
        /// <param name="pPath">the path to save the cached images to</param>
        private static void SaveCachedImages(string pPath)
        {
            // if the cached images have not been saved
            if ( (sCachedImages.Count > 0) && (!sCachedImages[0].Filename.StartsWith(pPath, StringComparison.CurrentCultureIgnoreCase)))
            {
                // save the cached images
                for (int i = 0; i < sCachedImages.Count; i++)
                {
                    // save the file name
                    string imageFile = Path.Combine(pPath, string.Format("image{0}.jpg", i));

                    // save the image into the given path as a jpg file
                    sCachedImages[i].Image.Save(imageFile, ImageFormat.Jpeg);

                    // update the cached images filename
                    sCachedImages[i].Filename = imageFile;
                }
            }
        }
        #endregion
    }
}
